<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>RPN 最终修正版 (Anchors First)</title>
    <style>
        body {
            font-family: 'Segoe UI', sans-serif;
            background-color: #1e1e1e;
            color: #d4d4d4;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 20px;
        }
        h1 { color: #569cd6; margin-bottom: 5px; }
        .subtitle { color: #9cdcfe; font-size: 14px; margin-bottom: 20px; }

        .main-container {
            display: flex;
            gap: 20px;
            background-color: #252526;
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.5);
        }

        canvas {
            background-color: #1e1e1e;
            border: 1px solid #333;
            border-radius: 5px;
        }

        .sidebar {
            width: 320px;
            display: flex;
            flex-direction: column;
            gap: 15px;
        }

        .step-card {
            background: #333;
            padding: 15px;
            border-radius: 5px;
            border-left: 4px solid #555;
            transition: all 0.3s;
            opacity: 0.5;
        }
        .step-card.active {
            background: #3c3c3c;
            border-left-color: #4CAF50;
            opacity: 1;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
            transform: translateX(-5px);
        }
        .step-title { font-weight: bold; color: #fff; margin-bottom: 5px; }
        .step-desc { font-size: 13px; color: #ccc; line-height: 1.4; }

        .controls {
            margin-top: 20px;
            display: flex;
            gap: 10px;
        }
        button {
            padding: 10px 20px;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
        }
        #btnPlay { background: #0e639c; }
        #btnStep { background: #e67e22; }
        #btnReset { background: #555; }

        .legend {
            margin-top: 15px;
            display: flex;
            gap: 15px;
            font-size: 12px;
            color: #aaa;
        }
        .dot { width: 10px; height: 10px; display: inline-block; margin-right: 5px; border-radius: 50%; }

        .status-bar { margin-top: 10px; color: #4CAF50; height: 20px; font-size: 14px;}
    </style>
</head>
<body>

    <h1>RPN 最终原理演示</h1>
    <div class="subtitle">逻辑：预设 Anchors &rarr; 提取特征 &rarr; 算出偏移 &rarr; 修正 Anchors</div>

    <div class="main-container">
        <canvas id="canvas" width="700" height="550"></canvas>

        <div class="sidebar">
            <div class="step-card" id="step0">
                <div class="step-title">0. 就位 (Setup) <span>📍</span></div>
                <div class="step-desc">
                    滑动窗口移动到新中心点。<br>
                    <span style="color:#569cd6; font-weight:bold;">9个预设 Anchors (虚线) 立即出现。</span><br>
                    它们是固定的参照物，现在还没对准物体。
                </div>
            </div>

            <div class="step-card" id="step1">
                <div class="step-title">1. 特征提取 (Context) <span>📷</span></div>
                <div class="step-desc">
                    网络开始看图 (3x3区域)。<br>
                    提取出 256维 特征向量 (黄色粒子)。<br>
                    <span style="color:#888">注意：此时 Anchors 保持静止。</span>
                </div>
            </div>

            <div class="step-card" id="step2">
                <div class="step-title">2. 并行计算 (Compute) <span>🧠</span></div>
                <div class="step-desc">
                    特征进入 1x1 卷积层。<br>
                    计算出两组数：得分 + <span style="color:#dcdcaa; font-weight:bold;">偏移量 (Reg)</span>。
                </div>
            </div>

            <div class="step-card" id="step3">
                <div class="step-title">3. 应用偏移 (Apply Shift) <span>✨</span></div>
                <div class="step-desc">
                    拿到偏移量后，开始动作。<br>
                    <span style="color:#4CAF50; font-weight:bold;">绿色实线框</span> 从虚线框中生长出来。<br>
                    这就是最终修正后的建议框。
                </div>
            </div>
        </div>
    </div>

    <div class="status-bar" id="statusText">准备就绪</div>

    <div class="controls">
        <button id="btnPlay" onclick="togglePlay()">⏯ 自动播放</button>
        <button id="btnStep" onclick="singleStep()">⏭ 单步执行</button>
        <button id="btnReset" onclick="reset()">↺ 重置</button>
    </div>

    <div class="legend">
        <span><span class="dot" style="border: 1px dashed #569cd6;"></span>预设 Anchors</span>
        <span><span class="dot" style="background:#f44336"></span>3x3 窗口</span>
        <span><span class="dot" style="background:#ffeb3b"></span>特征流</span>
        <span><span class="dot" style="border: 2px solid #4CAF50;"></span>修正后框</span>
    </div>

    <script>
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        const statusText = document.getElementById('statusText');

        // State
        let phase = 0;
        let isPlaying = true;
        let particles = [];
        let animFrameId;

        // Grid Config
        const offsetX = 50, offsetY = 50;
        const gridSize = 40;
        const gridW = 8, gridH = 8;
        let currGx = 1, currGy = 1;

        // Visual Elements Position
        const featBox = { x: 450, y: 80, w: 120, h: 50 };
        const clsHead = { x: 400, y: 200, w: 90, h: 40 };
        const regHead = { x: 540, y: 200, w: 90, h: 40 };
        // Anchor Zone is where we visualize the boxes
        const anchorZone = { x: 470, y: 400 };

        // Timers
        let phaseTimer = 0;
        const phaseDuration = [40, 60, 60, 100];

        function logicUpdate() {
            if(!isPlaying) return;
            phaseTimer++;
            if (phaseTimer > phaseDuration[phase]) advancePhase();
        }

        function advancePhase() {
            phaseTimer = 0;
            phase++;
            if(phase > 3) {
                phase = 0;
                // Move Grid Position
                currGx++;
                if(currGx > gridW - 2) {
                    currGx = 1; currGy++;
                    if(currGy > gridH - 2) currGy = 1;
                }
            }
            updateUI();
        }

        function singleStep() {
            isPlaying = false;
            statusText.innerText = "单步模式";
            statusText.style.color = "#e67e22";
            advancePhase();
        }

        function renderLoop() {
            logicUpdate();
            updateParticles();
            draw();
            animFrameId = requestAnimationFrame(renderLoop);
        }

        function updateParticles() {
            // Particle Spawning
            if (phase === 1) { // Extracting
                let startX = offsetX + currGx * gridSize;
                let startY = offsetY + currGy * gridSize;
                if(Math.random() > 0.5) spawnParticle(startX, startY, featBox.x + featBox.w/2, featBox.y + featBox.h/2, '#ffeb3b');
            } else if (phase === 2) { // Predicting
                let startX = featBox.x + featBox.w/2;
                let startY = featBox.y + featBox.h/2;
                if(Math.random() > 0.5) spawnParticle(startX, startY, clsHead.x + clsHead.w/2, clsHead.y, '#4fc1ff');
                if(Math.random() > 0.5) spawnParticle(startX, startY, regHead.x + regHead.w/2, regHead.y, '#dcdcaa');
            }

            // Move Particles
            particles.forEach((p, index) => {
                let dx = p.tx - p.x; let dy = p.ty - p.y;
                let dist = Math.sqrt(dx*dx + dy*dy);
                if(dist < 5) particles.splice(index, 1);
                else { p.x += (dx/dist) * 8; p.y += (dy/dist) * 8; }
            });
        }

        function draw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // 1. Grid
            drawGrid();

            // 2. Sliding Window
            let winX = offsetX + (currGx - 1.5) * gridSize;
            let winY = offsetY + (currGy - 1.5) * gridSize;
            ctx.strokeStyle = '#f44336'; ctx.lineWidth = 3; ctx.strokeRect(winX, winY, gridSize*3, gridSize*3);
            ctx.fillStyle = '#f44336'; ctx.beginPath(); ctx.arc(offsetX + currGx*gridSize, offsetY + currGy*gridSize, 4, 0, Math.PI*2); ctx.fill();

            // 3. Network Blocks
            drawBlock(featBox, "256-d Feature", "#ffeb3b", phase >= 1);
            drawBlock(clsHead, "Cls (1x1)", "#4fc1ff", phase >= 2);
            drawBlock(regHead, "Reg (1x1)", "#dcdcaa", phase >= 2);

            // Arrows
            if(phase >= 1) drawArrow(offsetX + currGx*gridSize + 40, offsetY + currGy*gridSize, featBox.x, featBox.y + 25);
            if(phase >= 2) {
                drawArrow(featBox.x + 60, featBox.y + 50, clsHead.x + 45, clsHead.y);
                drawArrow(featBox.x + 60, featBox.y + 50, regHead.x + 45, regHead.y);
            }
            if(phase >= 3) drawArrow(regHead.x + 45, regHead.y + 40, anchorZone.x, anchorZone.y - 60);

            // 4. Particles
            particles.forEach(p => { ctx.fillStyle = p.color; ctx.beginPath(); ctx.arc(p.x, p.y, 3, 0, Math.PI*2); ctx.fill(); });

            // 5. Anchors Visualization Logic (Key Change Here!)
            drawAnchorArea(anchorZone.x, anchorZone.y);
        }

        function drawAnchorArea(cx, cy) {
            // Draw Ghost Anchors (Visible from Phase 0)
            // This meets your requirement: Anchors appear immediately
            if (phase >= 0) {
                ctx.setLineDash([5, 5]);
                ctx.strokeStyle = "#569cd6";
                ctx.lineWidth = 1;
                // Anchor 1 (Square)
                ctx.strokeRect(cx - 20, cy - 20, 40, 40);
                // Anchor 2 (Wide)
                ctx.strokeRect(cx - 40, cy - 15, 80, 30);
                // Anchor 3 (Tall)
                ctx.strokeRect(cx - 15, cy - 40, 30, 80);
                ctx.setLineDash([]);

                ctx.fillStyle = "#569cd6"; ctx.font = "12px Arial"; ctx.textAlign = "center";
                if(phase < 3) ctx.fillText("预设 Anchors (等待修正...)", cx, cy + 60);
            }

            // Draw Transformation (Visible only in Phase 3)
            if (phase === 3) {
                let progress = isPlaying ? Math.min(1, phaseTimer / 40) : 1;

                ctx.strokeStyle = `rgba(76, 175, 80, ${progress})`;
                ctx.lineWidth = 2;

                // Proposal 1 (Shifted right)
                let offX = 15 * progress;
                ctx.strokeRect(cx - 20 + offX, cy - 20, 40, 40);

                // Proposal 2 (Shrunk width)
                let shrink = 20 * progress;
                ctx.strokeRect(cx - 40 + shrink/2, cy - 15, 80 - shrink, 30);

                // Proposal 3 (Shifted up)
                let offY = -15 * progress;
                ctx.strokeRect(cx - 15, cy - 40 + offY, 30, 80);

                ctx.fillStyle = "#4CAF50";
                ctx.fillText("偏移生效! (Final Proposals)", cx, cy + 60);
            }
        }

        function drawGrid() {
            ctx.strokeStyle = "#333"; ctx.lineWidth = 1;
            for(let i=0; i<=gridW; i++) { ctx.beginPath(); ctx.moveTo(offsetX + i*gridSize, offsetY); ctx.lineTo(offsetX + i*gridSize, offsetY + gridH*gridSize); ctx.stroke(); }
            for(let i=0; i<=gridH; i++) { ctx.beginPath(); ctx.moveTo(offsetX, offsetY + i*gridSize); ctx.lineTo(offsetX + gridW*gridSize, offsetY + i*gridSize); ctx.stroke(); }
            ctx.fillStyle = "#888"; ctx.font = "12px Arial"; ctx.textAlign="left"; ctx.fillText("Shared Feature Map", offsetX, offsetY - 10);
        }

        function drawBlock(rect, text, color, active) {
            ctx.fillStyle = active ? color : "#333"; ctx.strokeStyle = color; ctx.lineWidth = 2;
            ctx.fillRect(rect.x, rect.y, rect.w, rect.h); ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);
            ctx.fillStyle = active ? "#000" : "#888"; ctx.textAlign = "center"; ctx.font = "bold 12px Arial";
            ctx.fillText(text, rect.x + rect.w/2, rect.y + rect.h/2 + 4);
        }

        function drawArrow(x1, y1, x2, y2) {
            ctx.strokeStyle = "#666"; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke();
        }

        function spawnParticle(x, y, tx, ty, color) { particles.push({x, y, tx, ty, color}); }

        function updateUI() {
            for(let i=0; i<=3; i++) document.getElementById('step'+i).classList.remove('active');
            document.getElementById('step'+phase).classList.add('active');
        }

        function togglePlay() {
            isPlaying = !isPlaying;
            statusText.innerText = isPlaying ? "自动播放中..." : "已暂停";
            statusText.style.color = isPlaying ? "#4CAF50" : "#aaa";
        }

        function reset() {
            phase = 0; phaseTimer = 0; currGx = 1; currGy = 1; particles = []; isPlaying = true;
            statusText.innerText = "已重置"; updateUI();
        }

        updateUI();
        renderLoop();
    </script>
</body>
</html>